<?php

    // $mikron->Queries->Query('GAME', null, null, '`game` ASC', null, null, false, )
    //$a = Array('game', 'group'=>Array('game'), 'sum'=>Array('amount'=>summa), 'count'=>Array('*'=>'cnt'));
    //echo '<pre>';
    //print_r($a);

    /**
    * http://www.doctrine-project.org/documentation/manual/1_1/en/dql-doctrine-query-language
    * http://propel.phpdb.org/trac/wiki/Users/Documentation/1.3/BasicCRUD
    * http://www.sqlalchemy.org/docs/05/ormtutorial.html#common-filter-operators
    */

    /**
    * Пример использования критерионов и критериев
    */
    /*

    Samples:

    1)
    $cr = new Criteria();
    $cr->Add(new Criterion('login', 'guest'));
    $cr->Add(new Criterion('login', 'demo', CR_EQUAL, AO_OR));
    echo $cr->Compile();

    2)
    $cr = new Criteria
    (
        Array(
                new Criteria
                (
                        Array(
                        new Criterion('login', "SCINER", CR_EQUAL),
                        new Criterion('login', "Lenar", CR_EQUAL, AO_OR)
                        )
                ),
                new Criteria
                (
                    new Criterion('email', 'lenar%@%.ru', CR_LIKE)
                    , AO_AND
                )
              )
    );
    $out = ob_get_clean();
    echo $cr->Compile();
    exit;
    */

    define('FNC_LOWER', 'LOWER(%s)');
    define('FNC_UPPER', 'UPPER(%s)');
    /**
    * Константы для генерации запроса в функции чтения сущностей
    */
    define('CR_EQUAL', '=');
    define('CR_NOTEQUAL', '!=');
    define('CR_IN', 'IN');
    define('CR_LIKE', 'LIKE');
    define('CR_MORE', '>');
    define('CR_LESS', '<');
    define('CR_MOREOREQUAL', '>=');
    define('CR_LESSOREQUAL', '<=');
    define('CR_ISNULL', 'IS NULL');
    define('CR_NOTNULL', 'IS NOT NULL');

    define('AO_AND', 'AND');
    define('AO_OR', 'OR');
    
    class ReturnAttributes
    {
        private $attributes = Array();
        function count()
        {
            return count($this->attributes);
        }
        function attributes()
        {
            return $this->attributes;
        }
        function add($attributeName)
        {
            if(in_array($attributeName, $this->attributes))
            {
                return false;
            }
            $this->attributes[] = $attributeName;
            return true;
        }
        /**
        * Constructor. Run method ImplodeSQL() for SQL-query
        * 
        * @param mixed $attributes Array of attributes
        * @return ReturnAttributes
        */
        function __construct($attributes)
        {
            if(is_null($attributes))
            {
                return null;
            }
            if(!is_array($attributes))
            {
                $attributes = Array($attributes);
            }
            foreach($attributes as $attr)
            {
                $this->attributes[] = $attr;
            }
        }
        function ImplodeSQL()
        {
            if(count($this->attributes)>0)
            {
                return '`'.implode('`,`', $this->attributes).'`';
            }
            else
            {
                return '*';
            }
        }
    }

    class Criterion
    {
        private $field;
        private $operation;
        private $Criterion;
        private $andor;
        /**
        * Criterion for queries...
        * 
        * @param mixed $field Field name
        * @param mixed $Criterion Value for Criterion
        * @param mixed $operation CR_ const goes here
        * @param mixed $AndOr And/Or construct. AO_ const goes here
        * @param mixed $function FNC_ const goes here
        * @return Criterions
        */
        function __construct($field, $Criterion, $operation = CR_EQUAL, $AndOr = AO_AND, $function = null)
        {
            $this->operation = $operation;
            $this->andor = $AndOr;
            $pos = strpos($field, '(');
            if($pos === false)
            {
                $this->field = quote_smart($field);
                $this->field = str_replace("'", "`", $this->field);
            }
            else
            {
                $this->field = $field;
            }
            if(!is_null($function))
            {
                $this->field = sprintf($function, $this->field);
            }
            if(($operation == CR_IN) && is_array($Criterion))
            {
                $a = Array();
                foreach($Criterion as $cr)
                {
                    $a[] = quote_smart($cr);
                }
                $this->Criterion = '('.implode(', ', $a).')';
            }
            else
            {
                $this->Criterion = quote_smart($Criterion);
            }
            
        }
        function AndOr()
        {
            return $this->andor;
        }
        function Compile()
        {
            if(($this->operation == CR_ISNULL) || ($this->operation == CR_NOTNULL))
            {
                return sprintf(" %s %s ", $this->field, $this->operation);                
            }
            else
            {
                return sprintf(" %s %s %s ", $this->field, $this->operation, $this->Criterion);
            }
        }
    }

    class Criteria
    {
        private $Criterion;
        private $andor;
        /**
        * Add Criteria or Criterion
        * 
        * @param object $cr Criteria() or Criterion()
        */
        function Add($cr)
        {
            if(!is_object($cr))
            {
                throw new Exception("Type mismatch.");
            }
            $class = get_class($cr);
            if(($class != "Criteria") && ($class != "Criterion"))
            {
                throw new Exception("Type mismatch.");
            }
            if(is_null($this->Criterion))
            {
                $this->Criterion = Array($cr);
            }
            else
            {
                $this->Criterion[] = $cr;
            }
        }
        /**
        * Criterion for queries...
        * 
        * @param mixed $Criterion Criterion object or Array
        * @param mixed $AndOr And/Or construct. AO_ const goes here
        * @return Criteria
        */
        function __construct($Criterion = null, $AndOr = AO_AND)
        {
            $this->andor = $AndOr;
            if(is_array($Criterion))
            {
                $this->Criterion = $Criterion;                
            }
            elseif(!is_null($Criterion))
            {
                $this->Criterion = Array($Criterion);
            }
        }
        function AndOr()
        {
            return $this->andor;
        }
        function Compile()
        {
            $Criteria_assembly = null;
            foreach($this->Criterion as $Criterion)
            {
                $cr = $Criterion->Compile();
                if(is_null($Criteria_assembly))
                {
                    $Criteria_assembly = $cr;
                }
                else
                {
                    $Criteria_assembly .= sprintf(" %s %s ", $Criterion->AndOr(), $cr);
                }
            }
            return sprintf("(%s)", $Criteria_assembly);
        }
    }

    /**
    * Compile condition string for SQL-queries from Criterias
    * 
    * @param mixed $conditions
    */
    function CompileConditionString($conditions)
    {
        if(is_string($conditions))
        {
            return $conditions;
        }
        elseif(is_object($conditions))
        {
            if(get_class($conditions)=="Criteria" || get_class($conditions)=="Criterion")
            {
                return $conditions->Compile();
            }
            else
            {
                throw new Exception("Incorrect Criterion for CompileConditionString() method in ".__FILE__." at line ".__LINE__.".");
            }
        }
    }

    /**
    * Запросы
    */
    class Queries
    {
        private $mikron;
        static private $transaction_started = false;
        function __construct($mikron)
        {
            $this->mikron = $mikron;
        }
        /**
        * Старт транзакции
        */
        static function StartTransaction()
        {
            if(self::$transaction_started)
            {
                return false;
            }
            @mysql_query("START TRANSACTION;") or Exception2(mysql_error());
            self::$transaction_started = true;
        }
        static function CommitTransaction()
        {
            if(!self::$transaction_started)
            {
                throw new Exception("Не удалось подтвердить транзакцию, т.к. она не была запущена ранее.");
            }
            @mysql_query("COMMIT;") or Exception2(mysql_error());
            self::$transaction_started = false;
        }

        static function RollbackTransaction()
        {
            if(!self::$transaction_started)
            {
                throw new Exception("Не удалось откатить транзакцию, т.к. она не была запущена ранее.");
            }
            @mysql_query("ROLLBACK;") or Exception2(mysql_error());
            self::$transaction_started = false;
        }

        public function QueryCount($entityname, $aId = null, $conditions = null, $order = null, $limit = null)
        {
            $calculate_count_only = true;
            return $this->Query($entityname, $aId, $conditions, $order, $limit, null, $calculate_count_only);
        }

        public function QueryAsArray($entityname, $aId = null, $conditions = null, $order = null, $limit = null, $parent = null, $calculate_count_only = false, $readed_fields = null)
        {
            $ret= Array();
            $result = $this->Query($entityname, $aId, $conditions, $order, $limit, $parent, $calculate_count_only, $readed_fields);
            while($item = $result->fetch())
            {
                $ret[] = $item;
            }
            return $ret;
        }

        public function Delete($entityname, $conditions = null)
        {
            global $mikron;
            if(!$mikron->EntityManager->CheckEntityName($entityname))
            {
                throw new Exception('Unknown entity name '.var_export($entityname, true));
            }
            $sql = "DELETE FROM $entityname WHERE ".$conditions->Compile();
            $result = mysql_query($sql);
            if(mysql_errno())
            {
                echo $sql, '<br />';
                throw new Exception('Error in file '.__FILE__.' on line '.__LINE__.', '.mysql_error());
            }
            return mysql_affected_rows();
        }

        /**
        * put your comment there...
        * 
        * @param string $entityname
        * @param int $aId
        * @param mixed $conditions
        * @param string $order
        * @param string $limit
        * @param object $parent
        * @param boolean $calculate_count_only
        * @param mixed $readed_fields ReturnAttributes object
        */
        public function QueryOne($entityname, $aId = null, $conditions = null, $order = null, $limit = null, $parent = null, $calculate_count_only = false, $readed_fields = null)
        {
            $qr = $this->Query($entityname, $aId, $conditions, $order, $limit, $parent, $calculate_count_only, $readed_fields);
            if($qr->count() == 1)
            {
                return $qr->fetch();
            }
            return null;
        }

        public function Query($entityname, $aId = null, $conditions = null, $order = null, $limit = null, $parent = null, $calculate_count_only = false, $readed_fields = null)
        {
            $sql = $this->mikron->EntityManager->CompileQueryParameters($entityname, $aId, $conditions, $order, $limit, $readed_fields, $calculate_count_only);
            $result = mysql_query($sql);
            if(mysql_errno())
            {
                echo $sql, '<br />';
                throw new Exception('Error in file '.__FILE__.' on line '.__LINE__.', '.mysql_error());
            }
            if($calculate_count_only)
            {
                $row = mysql_fetch_object($result);
                return $row->cnt;
            }
            return new QueryClass($result, $entityname);
        }
        
        public static function SaveEditedEntity($object, $data_for_save)
        {
            $entityname = get_class($object);
            $id = $object->id;
            // Обязательно высчитываем видимое имя для объекта
            $data_for_save['mikron_displayname'] = $object->DisplayName();
            $data = Array();
            foreach($data_for_save as $field => $value)
            {
                if(is_null($value))
                {
                    $value = 'NULL';
                }
                else
                {
                    $value = quote_smart($value);
                }
                $data[] = "`$field`=".$value;
            }
            $data2 = implode(', ', $data);
            $sql = "UPDATE $entityname SET $data2 WHERE `id` = ".$id;
            // Запрос к БД. При ошибке генерируется исключение.
            $res = @mysql_query($sql) or
            Exception2("Ошибка сохранения сущности $entityname.<br /><code>$sql</code><br />".mysql_error());
            MikronBaseObject::DropModifiedFlags($object);
        }

        public static function SaveNewEntity($object, $data_for_save)
        {
            $entityname = get_class($object);
            // Порядок сортировки по умолчанию
            $mikron_order = 1;
            // Расчет порядка сортировки на основе максимального имеющегося
            // в таблице сущностей данного типа
            $sql = "SELECT MAX(`mikron_order`) AS maxordervalue FROM $entityname";
            $result = @mysql_query($sql) or Exception2("Ошибка сохранения сущности $entityname.<br /><code>$sql</code><br />".mysql_error());
            if(!mysql_errno())
            {
                $mikron_order = (int)mysql_result($result, 0, 'maxordervalue');
                mysql_free_result($result);
                $mikron_order++;
            }
            // Обязательно высчитываем видимое имя для объекта
            $data_for_save['mikron_displayname'] = $object->DisplayName();
            // Порядок сортировки
            $data_for_save['mikron_order'] = $mikron_order;
            $fields = Array();
            $values = Array();
            foreach($data_for_save as $field => $value)
            {
                $fields[] = "`$field`";
                if(is_null($value))
                {
                    $values[] = 'NULL';
                }
                else
                {
                    $values[] = quote_smart($value);
                }
            }
            $fields2 = implode(', ', $fields);
            $values2 = implode(', ', $values);
            $sql = "INSERT INTO $entityname($fields2) VALUES($values2)";
            // Запрос к БД. При ошибке генерируется исключение.
            $res = @mysql_query($sql) or Exception2("Ошибка сохранения
            сущности $entityname.<br /><code>$sql</code><br />".mysql_error());
            // Заполнение базовых атрибутов
            $object->mikron['order'] = $mikron_order;
            $object->SetId(mysql_insert_id());
            MikronBaseObject::DropModifiedFlags($object);
        }

    }
    
    class QueryClass
    {
        private $result = null;
        private $entityname = null;
        public $fields = null;
        private $count = 0;
        function __construct($result, $entityname)
        {
            if(!is_null($result))
            {
                $this->count = mysql_num_rows($result);
            }
            $this->result = $result;
            $this->entityname = $entityname;
            $ent = new $entityname();
            $this->fields = $ent->fields;
        }
        public function classname()
        {
            return $this->entityname;
        }
        public function count()
        {
            return $this->count;
        }
        function fetch()
        {
            $fields = null;
            while($object = mysql_fetch_object($this->result))
            {
                return new $this->entityname($object);
            }
            return false;
        }
        function reset()
        {
            mysql_data_seek($this->result, 0);
        }
        public function free()
        {
            mysql_free_result($this->result);
            $this->count = 0;
            $this->entityname = null;
            $this->fields = null;
        }
    }

?>